package com.cqdev.washer.biz.service;

import com.cqdev.washer.biz.enums.ProjectExecuteStatusEnum;
import com.cqdev.washer.biz.model.DbConfigBO;
import com.cqdev.washer.biz.model.ProjectBO;
import com.cqdev.washer.biz.model.SqlScriptBO;
import com.cqdev.washer.infra.entity.Project;
import com.cqdev.washer.infra.entity.ProjectExample;
import com.cqdev.washer.infra.entity.SqlScript;
import com.cqdev.washer.infra.entity.SqlScriptExample;
import com.cqdev.washer.infra.mapper.ProjectMapper;
import com.cqdev.washer.infra.mapper.SqlScriptMapper;
import com.cqdev.washer.infra.util.keygen.KeyGenerators;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author yunfei.li
 *         类描述: TODO
 *         日期： 2018/6/22
 */
@Service
@Transactional(rollbackFor = Exception.class)
public class ProjectService {

    private final ProjectMapper projectMapper;
    private final SqlScriptMapper sqlScriptMapper;

    private final DbConfigService dbConfigService;
    private static List<Long> availableProjectIdList = new ArrayList<>();

    public ProjectService(ProjectMapper projectMapper, SqlScriptMapper sqlScriptMapper, DbConfigService dbConfigService) {
        this.projectMapper = projectMapper;
        this.sqlScriptMapper = sqlScriptMapper;
        this.dbConfigService = dbConfigService;
    }

    @PostConstruct
    void init() {
        List<Project> projectList = projectMapper.selectByExample(new ProjectExample());
        List<Long> idList = projectList.stream().map(value -> value.getId()).collect(Collectors.toList());
        availableProjectIdList = idList;
    }

    public List<ProjectBO> listAll() {
        ProjectExample query = new ProjectExample();

        return convertProject(projectMapper.selectByExample(query));
    }

    public ProjectBO getOne(Long projectId) {

        List<ProjectBO> projectBOList = convertProject(Collections.singletonList(projectMapper.selectByPrimaryKey(projectId)));

        if (CollectionUtils.isEmpty(projectBOList)) {
            return null;
        }
        return projectBOList.get(0);
    }

    public SqlScriptBO getScriptOne(Long id) {
        List<SqlScriptBO> sqlScriptBOList = convertSqlScript(Collections.singletonList(sqlScriptMapper.selectByPrimaryKey(id)));
        if (sqlScriptBOList.isEmpty()) {
            return null;
        }
        return sqlScriptBOList.get(0);
    }


    private List<ProjectBO> convertProject(List<Project> projectList) {
        if (CollectionUtils.isEmpty(projectList)) {
            return Collections.emptyList();
        }
        List<ProjectBO> collect = projectList.stream().map(project -> {

            DbConfigBO dbConfigBO = dbConfigService.getOne(project.getDbConfigId());
            SqlScriptExample query = new SqlScriptExample();
            query.createCriteria().andProjectIdEqualTo(project.getId());
            List<SqlScript> sqlScriptList = sqlScriptMapper.selectByExampleWithBLOBs(query);

            ProjectBO bo = new ProjectBO();

            bo.setId(project.getId());
            bo.setName(project.getName());
            bo.setDbConfigBO(dbConfigBO);
            bo.setSqlScriptBOList(convertSqlScript(sqlScriptList));
            bo.setProjectExecuteStatusEnum(getProjectStatus(project.getId()));

            return bo;
        }).collect(Collectors.toList());
        return collect;
    }

    private List<SqlScriptBO> convertSqlScript(List<SqlScript> sqlScriptList) {
        if (CollectionUtils.isEmpty(sqlScriptList)) {
            return Collections.emptyList();
        }
        return sqlScriptList.stream().map(sqlScript ->
                new SqlScriptBO(
                        sqlScript.getId(),
                        sqlScript.getScript(),
                        sqlScript.getName(),
                        sqlScript.getDescription(),
                        sqlScript.getSn(),
                        sqlScript.getLastExecuteTime(),
                        sqlScript.getLastExecuteStatus(),
                        sqlScript.getExecuteCount(),
                        sqlScript.getLastElapsedTime()
                )).sorted(Comparator.comparing(SqlScriptBO::getSn)).collect(Collectors.toList());

    }

    public void save(ProjectBO projectBO) {
        if (projectBO.getId() == null) {
            saveNewProjectList(Collections.singletonList(projectBO));
        } else {
            updateExistedProjectList(Collections.singletonList(projectBO));
        }

    }

    private void updateExistedProjectList(List<ProjectBO> projectBOList) {
        //update all
        if (CollectionUtils.isEmpty(projectBOList)) {
            return;
        }
        projectBOList.forEach(projectBO -> {
            Project projectDO = new Project(
                    projectBO.getId(),
                    projectBO.getName(),
                    projectBO.getDbConfigBO().getId()
            );
            projectMapper.updateByPrimaryKey(projectDO);
            dbConfigService.saveOrUpdate(projectBO.getDbConfigBO());
            List<SqlScriptBO> sqlScriptBOList = projectBO.getSqlScriptBOList();
            saveScriptAll(sqlScriptBOList, projectBO.getId());
        });

    }

    private void saveScriptAll(List<SqlScriptBO> sqlScriptBOList, Long projectId) {
        List<SqlScriptBO> newSqlScriptList = sqlScriptBOList.stream().filter(sqlScriptBO -> sqlScriptBO.getId() == null).collect(Collectors.toList());
        saveNewSqlScriptList(newSqlScriptList, projectId);
        List<SqlScriptBO> existedSqlScriptList = sqlScriptBOList.stream().filter(sqlScriptBO -> sqlScriptBO.getId() != null).collect(Collectors.toList());
        updateExistedSqlScriptList(existedSqlScriptList);
    }

    private void updateExistedSqlScriptList(List<SqlScriptBO> existedSqlScriptList) {
        if (CollectionUtils.isEmpty(existedSqlScriptList)) {
            return;
        }
        List<SqlScript> existedSqlScriptBOList = existedSqlScriptList.stream().map(sqlScriptBO -> new SqlScript(
                sqlScriptBO.getId(),
                sqlScriptBO.getSn(),
                sqlScriptBO.getName(),
                sqlScriptBO.getDescription(),
                new Date(),
                sqlScriptBO.getScript()
        )).collect(Collectors.toList());

        existedSqlScriptBOList.forEach(sqlScriptMapper::updateByPrimaryKey);
    }

    private void saveNewSqlScriptList(List<SqlScriptBO> newSqlScriptBOList, Long projectId) {
        if (CollectionUtils.isEmpty(newSqlScriptBOList)) {
            return;
        }

        List<SqlScript> newSqlScriptDOLIst = newSqlScriptBOList.stream().map(sqlScriptBO -> new SqlScript(
                KeyGenerators.generateKeyToLong(),
                projectId,
                sqlScriptBO.getSn(),
                sqlScriptBO.getName(), sqlScriptBO.getDescription(), new Date(), new Date(), sqlScriptBO.getScript()
        )).collect(Collectors.toList());

        newSqlScriptDOLIst.forEach(sqlScriptMapper::insert);
    }

    private void saveNewProjectList(List<ProjectBO> projectBOList) {
        if (CollectionUtils.isEmpty(projectBOList)) {
            return;
        }
        projectBOList.forEach(projectBO -> {
            Project project = new Project(
                    KeyGenerators.generateKeyToLong(),
                    projectBO.getName(),
                    projectBO.getDbConfigBO().getId()
            );


            saveProjectDO(project);
            dbConfigService.saveOrUpdate(projectBO.getDbConfigBO());
        });
    }

    private void saveProjectDO(Project project) {
        projectMapper.insert(project);
    }

    public void start(ProjectBO one) {
        if (availableProjectIdList.contains(one.getId())) {
            availableProjectIdList.remove(one.getId());
        }
    }

    public void shutdown(ProjectBO one) {
        availableProjectIdList.add(one.getId());
    }

    private ProjectExecuteStatusEnum getProjectStatus(Long id) {
        if (availableProjectIdList.contains(id)) {
            return ProjectExecuteStatusEnum.FREE;
        }
        return ProjectExecuteStatusEnum.DOING;
    }
}
